from typing import List, Dict, Any, Union, Optional, Callable, Tuple
from datetime import datetime, timedelta, timezone

from apps.app_tx_meeting.models import MeetingRoom, MeetingReservation, MeetingRoomType
from apps.app_tx_meeting.managers import MeetingReservationManager
from .time_line_sweeper import TimeLine, TimeLineSweeper
from .meet_reservation import MeetReservationHelper


class RoomReserveTimeIntervalCounter:
    """
    会议室已预定数时间间隔统计器
    """

    @staticmethod
    def meeting_room_usable(
            query_interval: TimeLine, room_type: Optional[int] = None, is_admin: bool = False
    ) -> List[Dict[str, int]]:
        """
        时间段指定类型的会议室资源已预定数

        :param query_interval: 查询时间区间
        :param room_type: 会议室类型
        :param is_admin: 管理员可以看到 普通用户不可见的会议室

        :return: [
            {
                "room_type": int,
                "reserved": int,
                "total": int
            }
        ]
        """
        room_pools = MeetingRoom.get_main_instance()
        if room_type:
            all_room_types = [room_type]
        else:
            all_room_types = [
                MeetingRoomType.F_50.value,
                MeetingRoomType.F_100.value,
                MeetingRoomType.F_300.value,
                MeetingRoomType.F_500.value,
                MeetingRoomType.F_1000.value
            ]

        # 剔除不可见会议室
        room_pools_dict = {}
        visible_room_types = []
        for m_type in all_room_types:
            room_num, visible = room_pools.get_room_config(room_type=m_type)
            if not visible and not is_admin:
                continue

            visible_room_types.append(m_type)
            room_pools_dict[m_type] = room_num

        if not visible_room_types:
            return []

        time_lines_dict = RoomReserveTimeIntervalCounter.get_meeting_reservation_intervals(
            time_interval=query_interval, room_type=room_type)
        ret = []
        for m_type in visible_room_types:
            time_lines = time_lines_dict.get(m_type, [])
            room_total = room_pools_dict.get(m_type, 0)
            sweeper = TimeLineSweeper(time_lines=time_lines)
            num = sweeper.range_max_concurrent(sweep_range=query_interval, contain_sweep_range=False)
            ret.append({'room_type': m_type, 'reserved': num, 'total': room_total})

        return ret

    def meeting_room_usable_internals(
            self, time_interval: TimeLine, room_type: int = None
    ) -> List[Dict[str, Union[int, List[Dict[str, Any]]]]]:
        """
        会议室资源可预定数区间统计

        :param time_interval: 查询的时间段
        :param room_type: 会议室类型
        :retrun: [
            {
                "room_type": int,
                "intervals": [
                    {
                        "start": int,
                        "end": int,
                        "reservable": int,
                        "total": int
                    }
                ]
            }
        ]
        """
        room_pools = MeetingRoom.get_main_instance()
        if room_type:
            all_room_types = [room_type]
        else:
            all_room_types = [
                MeetingRoomType.F_50.value,
                MeetingRoomType.F_100.value,
                MeetingRoomType.F_300.value,
                MeetingRoomType.F_500.value,
                MeetingRoomType.F_1000.value
            ]

        # 剔除不可见会议室
        room_pools_dict = {}
        visible_room_types = []
        for m_type in all_room_types:
            room_num, visible = room_pools.get_room_config(room_type=m_type)
            if visible:
                visible_room_types.append(m_type)
                room_pools_dict[m_type] = room_num

        if not visible_room_types:
            return []

        granularity_minutes = 30
        step = 60 * granularity_minutes
        align_interval = self.align_time_interval(time_interval=time_interval, granularity_minutes=granularity_minutes)
        # 可预定要考虑 预定前后多预留的时间
        reserve_time_adder = MeetReservationHelper.room_reserve_before_after_time_adder()
        start, end = reserve_time_adder(align_interval.start, align_interval.end)
        query_time_range = TimeLine(start=start, end=end)
        time_lines_dict = self.get_meeting_reservation_intervals(time_interval=query_time_range, room_type=room_type)

        ret = []
        for m_type in visible_room_types:
            time_lines = time_lines_dict.get(m_type, [])
            room_total = room_pools_dict.get(m_type, 0)
            intervals = self._room_usable_internals(
                time_lines=time_lines, room_total=room_total,
                time_interval=align_interval, step=step, reserve_time_adder=reserve_time_adder
            )
            ret.append({'room_type': m_type, 'intervals': intervals})

        return ret

    @staticmethod
    def _room_usable_internals(
            time_lines: List[TimeLine], room_total: int, time_interval: TimeLine, step: int,
            reserve_time_adder: Callable[[int, int], Tuple[int, int]]
    ) -> List[Dict[str, int]]:
        """
        按指定时间间隔统计每个时间间隔内已预定会议室数量

        :param time_lines: 已预定的与查询时间区间有重叠的会议的时间段
        :param room_total: 会议室总数
        :param time_interval: 查询时间区间
        :param step: 时间间隔，秒
        :param reserve_time_adder: 预定时间段 加上前后多预留时间计算器
        """
        loop_times = (time_interval.end - time_interval.start) // step + 1

        intervals = []
        sweeper = TimeLineSweeper(time_lines=time_lines)
        start = time_interval.start
        for x in range(loop_times):
            end = start + step
            # 每个“间隔”都要加上 预定前后多预留的时间，再判断“间隔”是否可预定
            sweep_start, sweep_end = reserve_time_adder(start, end)
            num = sweeper.range_max_concurrent(
                sweep_range=TimeLine(start=sweep_start, end=sweep_end), contain_sweep_range=False)
            intervals.append({
                'start': start,
                'end': end,
                'reservable': room_total - num,
                'total': room_total
            })
            start = end
            if start >= time_interval.end:
                break

        return intervals

    def get_room_reserved_internals(
            self, time_interval: TimeLine, room_type: int = None
    ) -> List[Dict[str, Union[int, List[Dict[str, Any]]]]]:
        """
        :param time_interval: 查询的时间段
        :param room_type: 会议室类型
        :retrun: [
            {
                "room_type": int,
                "intervals": [
                    {
                        "start": int,
                        "end": int,
                        "reserved": int,
                        "total": int
                    }
                ]
            }
        ]
        """
        granularity_minutes = 30
        step = 60 * granularity_minutes
        align_interval = self.align_time_interval(time_interval=time_interval, granularity_minutes=granularity_minutes)
        time_lines_dict = self.get_meeting_reservation_intervals(time_interval=align_interval, room_type=room_type)
        room_pools = MeetingRoom.get_main_instance()
        room_pools_dict = {
            MeetingRoomType.F_50.value: room_pools.type_50_num,
            MeetingRoomType.F_100.value: room_pools.type_100_num,
            MeetingRoomType.F_300.value: room_pools.type_300_num,
            MeetingRoomType.F_500.value: room_pools.type_500_num,
            MeetingRoomType.F_1000.value: room_pools.type_1000_num
        }

        if room_type:
            all_room_types = [room_type]
        else:
            all_room_types = [
                MeetingRoomType.F_50.value,
                MeetingRoomType.F_100.value,
                MeetingRoomType.F_300.value,
                MeetingRoomType.F_500.value,
                MeetingRoomType.F_1000.value
            ]

        ret = []
        for m_type in all_room_types:
            time_lines = time_lines_dict.get(m_type, [])
            room_total = room_pools_dict.get(m_type, 0)
            intervals = self._room_reserved_count_internals(
                time_lines=time_lines, room_total=room_total,
                time_interval=align_interval, step=step
            )
            ret.append({'room_type': m_type, 'intervals': intervals})

        return ret

    @staticmethod
    def _room_reserved_count_internals(
            time_lines: List[TimeLine], room_total: int, time_interval: TimeLine, step: int
    ) -> List[Dict[str, int]]:
        """
        按指定时间间隔统计每个时间间隔内已预定会议室数量

        :param time_lines: 已预定的与查询时间区间有重叠的会议的时间段
        :param room_total: 会议室总数
        :param time_interval: 查询时间区间
        :param step: 时间间隔，秒
        """
        loop_times = (time_interval.end - time_interval.start) // step + 1

        intervals = []
        sweeper = TimeLineSweeper(time_lines=time_lines)
        start = time_interval.start
        for x in range(loop_times):
            end = start + step
            num = sweeper.range_max_concurrent(sweep_range=TimeLine(start=start, end=end), contain_sweep_range=False)
            intervals.append({
                'start': start,
                'end': end,
                'reserved': num,
                'total': room_total
            })
            start = end
            if start >= time_interval.end:
                break

        return intervals

    @staticmethod
    def get_meeting_reservation_intervals(time_interval: TimeLine, room_type: int = None) -> Dict[int, List[TimeLine]]:
        """
        查询与指定时间段有重叠的会议预定，按预定的会议室类型分组已预定的时间段
        """
        qs = MeetingReservationManager.get_overlapping_reservations(
            start=time_interval.start, end=time_interval.end, room_type=room_type
        )
        qs = qs.values('room_reserve_start', 'room_reserve_end', 'room_type')
        intervals_dict = {
            MeetingRoomType.F_50.value: [],
            MeetingRoomType.F_100.value: [],
            MeetingRoomType.F_300.value: [],
            MeetingRoomType.F_500.value: [],
            MeetingRoomType.F_1000.value: []
        }
        for obj in qs:
            if obj['room_type'] in intervals_dict:
                intervals_dict[obj['room_type']].append(
                    TimeLine(start=obj['room_reserve_start'], end=obj['room_reserve_end']))

        return intervals_dict

    def align_time_interval(self, time_interval: TimeLine, granularity_minutes: int) -> TimeLine:
        """
        按时间颗粒度 外扩对齐一个时间区间，整时开始对齐

            时间粒度10分钟时：[8:35, 12:33] -> [8:30, 12:40]
            时间粒度15分钟时：[8:35, 12:33] -> [8:30, 12:45]
            时间粒度20分钟时：[8:35, 12:33] -> [8:20, 12:40]
            时间粒度30分钟时：[8:35, 12:33] -> [8:30, 13:00]
            时间粒度60分钟时：[8:35, 12:33] -> [8:00, 13:00]

        :param time_interval: 要对齐的时间区间
        :param granularity_minutes: 时间粒度分钟数, 可选10-60
        """
        if not (10 <= granularity_minutes <= 60):
            raise Exception('时间粒度只能为10-60分钟')

        start_ts = self._forward_align_timestamp(ts=time_interval.start, granularity_minutes=granularity_minutes)
        end_ts = self._backward_align_timestamp(ts=time_interval.end, granularity_minutes=granularity_minutes)
        return TimeLine(start=start_ts, end=end_ts)

    @staticmethod
    def _forward_align_timestamp(ts: int, granularity_minutes: int) -> int:
        """
        向前对齐 backward
        """
        dt = datetime.fromtimestamp(ts, tz=timezone.utc)
        pre_tm = dt.replace(minute=0, second=0, microsecond=0)
        for i in range(60):
            next_tm = pre_tm + timedelta(minutes=granularity_minutes)
            if next_tm > dt:
                return int(pre_tm.timestamp())

            pre_tm = next_tm

        return int(dt.replace(minute=0, second=0, microsecond=0).timestamp())

    @staticmethod
    def _backward_align_timestamp(ts: int, granularity_minutes: int) -> int:
        """
        向后对齐
        """
        dt = datetime.fromtimestamp(ts, tz=timezone.utc)
        next_tm = dt.replace(minute=0, second=0, microsecond=0)
        for i in range(60):
            if next_tm >= dt:
                return int(next_tm.timestamp())

            next_tm += timedelta(minutes=granularity_minutes)

        return int((dt.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1)).timestamp())

    @staticmethod
    def meeting_room_reserved_count(query_interval: TimeLine, room_type: int, contain_query_interval: bool) -> int:
        """
        时间段指定类型的会议室资源已预定数

        :param query_interval: 查询时间区间
        :param room_type: 会议室类型
        :param contain_query_interval: 是否包含查询区间；比如查询区间是要预定的时间段，包含要预定的时间段，需要多少会议室
        """
        qs = MeetingReservationManager.get_overlapping_reservations(
            start=query_interval.start, end=query_interval.end, room_type=room_type)
        qs = qs.values('room_reserve_start', 'room_reserve_end', 'room_type')
        time_lines = [
            TimeLine(
                start=x['room_reserve_start'], end=x['room_reserve_end']
            ) for x in qs if x['room_type'] == room_type
        ]
        sweeper = TimeLineSweeper(time_lines=time_lines)
        num = sweeper.range_max_concurrent(sweep_range=query_interval, contain_sweep_range=contain_query_interval)
        return num
